﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Net.Mail;
using System.Net.Mime;
using System.Runtime.Serialization;
using K3R.Core;
using System.Collections;
using System.Linq;
using System;
using System.Text.RegularExpressions;

namespace K3R.Email {
	public delegate void EmailSentEventHandler(IEmailMessage email);
	public delegate void EmailNotSentEventHandler(IEmailMessage email, Exception exception);
	public delegate void NewEmailsDetectedEventHandler(int count);

	[DataContract]
	public class EmailQueue<TEmail> : List<IEmailMessage> where TEmail: IEmailMessage, new() {
		#region Protected Members
		protected SmtpClient _smtp = null;
		#endregion

		#region Properties
		[DataMember(Name = "running")] public bool Running { get; private set; }
		#endregion

		#region Events
		public event EmailSentEventHandler EmailSent;
		public event EmailNotSentEventHandler EmailNotSent;
		public event NewEmailsDetectedEventHandler NewEmailsDetected;
		#endregion

		#region Constructors
		public EmailQueue()
			: base() {
			_smtp = new SmtpClient();
			_smtp.SendCompleted += new SendCompletedEventHandler(smtp_SendCompleted);

			Running = false;
		}
		#endregion

		#region Instance
		public void Refresh() {
			if (!Running) {
				Running = true;
				this.Clear();
				this.AddRange(Load());
				Running = false;
			}
		}
		#endregion

		#region Run
		public void Run(bool refresh, bool async) {
			if (refresh)
				Refresh();

			if (!Running) {
				Running = true;

				try {
					if (this.Count > 0) {
						if (NewEmailsDetected != null)
							NewEmailsDetected(this.Count);

						Dictionary<int, EmailTemplate> cache = new Dictionary<int, EmailTemplate>();
						foreach (IEmailMessage email in this) {
							if (!Running)
								break;

							if (new Regex(@"[a-zA-Z0-9._%+-]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}").IsMatch(email.Address)) {
								EmailTemplate template;
								if (!cache.TryGetValue(email.Type, out template)) {
									cache.Add(email.Type, EmailTemplate.Load(email.Type));
									template = cache[email.Type];
								}

								string subject = template.Title;
								string body = email.TextReplace(template.Body);

								MailMessage message = new MailMessage { Subject = subject };
								message.To.Add(email.Address);

								if (template.IsHtml)
									message.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(body, new ContentType("text/html")));
								else message.Body = body;

								if (async)
									SendAsync(message, email);
								else Send(message, email);
							}
							else {
								email.MarkAsCancelled();

								if (EmailNotSent != null)
									EmailNotSent(email, new Exception("Email address is invalid."));
							}

						}
					}
				}
				finally {
					Running = false;
				}
			}
		}

		public void Run(bool async) {
			Run(true, async);
		}

		public void Run() {
			Run(false);
		}

		public void CancelRun() {
			_smtp.SendAsyncCancel();
			Running = false;
		}
		#endregion

		#region Send
		protected void Send(MailMessage message, IEmailMessage email) {
			email.MarkAsSending();

			try {
				_smtp.Send(message);
				email.MarkAsSent();

				if (EmailSent != null)
					EmailSent(email);
			}
			catch (Exception error) {
				email.MarkAsNotSending();

				if (EmailNotSent != null)
					EmailNotSent(email, error);
			}
		}

		protected void SendAsync(MailMessage message, IEmailMessage email) {
			email.MarkAsSending();
			_smtp.SendAsync(message, email);
		}

		protected void smtp_SendCompleted(object sender, AsyncCompletedEventArgs e) {
			IEmailMessage email = (IEmailMessage)e.UserState;

			if (e.Cancelled) {
				email.MarkAsCancelled();

				if (EmailNotSent != null)
					EmailNotSent(email, new Exception("Send Cancelled."));
			}
			else if (e.Error != null) {
				email.MarkAsNotSending();

				if (EmailNotSent != null)
					EmailNotSent(email, e.Error);
			}
			else {
				email.MarkAsSent();

				if (EmailSent != null)
					EmailSent(email);
			}
		}
		#endregion

		#region Static
		public IEnumerable<IEmailMessage> Load() {
			return new TEmail().List();
		}
		#endregion
	}
}
